/* Copyright (c) 2022 渝州大数据实验室
 *
 * Lanius is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *     http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.yzbdl.lanius.orchestrate.serv.database;

import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import com.alibaba.druid.pool.DruidDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.yzbdl.lanius.orchestrate.common.base.dto.DataBaseInfo;
import org.yzbdl.lanius.orchestrate.common.dto.resource.*;
import org.yzbdl.lanius.orchestrate.common.exception.runtime.BusinessException;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static org.yzbdl.lanius.orchestrate.serv.constant.TaskResourceConstant.*;

/**
 * 数据库资源库处理顶层抽象类
 *
 * @author hujian@yzbdl.ac.cn
 * @date 2022-09-20 16:42
 */
@Slf4j
public abstract class AbstractDbRepositoryInvokeHandler implements DbRepositoryInvokeHandler{

    /**
     * 测试数据库连接超时时间(ms)
     */
    protected static final String CONNECT_DB_TIME_OUT = "5000";

    /**
     * 验证数据库连接有效超时时间(s)
     */
    protected static final Integer CONNECT_DB_SOCKET_TIME_OUT = 5;

    /**
     * jdbc模板ip占位符
     */
    protected static final String JDBC_URL_TEMPLATE_IP_PLACEHOLDER = "{ip}";

    /**
     * jdbc模板port占位符
     */
    protected static final String JDBC_URL_TEMPLATE_PORT_PLACEHOLDER = "{port}";

    /**
     * jdbc模板database占位符
     */
    protected static final String JDBC_URL_TEMPLATE_DATABASE_PLACEHOLDER = "{database}";

    /**
     * jdbc模板扩展参数占位符
     */
    protected static final String JDBC_URL_TEMPLATE_EXT_PARAMS_PLACEHOLDER = "{params}";

    /**
     * 通过Druid获取数据库连接对象
     * @return 数据库连接对象
     */
    @Override
    public DruidDataSource getDruidDataSource(DataBaseInfo dbInfo) {
        DruidDataSource ds = new DruidDataSource();
        ds.setUrl(dbInfo.getUrl());
        ds.setUsername(dbInfo.getUserName());
        ds.setPassword(dbInfo.getPassword());
        // 失败重试机制
        ds.setConnectionErrorRetryAttempts(3);
        // 请求失败后中断
        ds.setBreakAfterAcquireFailure(true);
        // 最长等待时间
        ds.setMaxWait(5000);
        return ds;
    }

    /**
     * 生成JdbcUrl连接
     * @param jdbcTemplate url模板
     * @param ip ip地址
     * @param port 端口
     * @param database 数据库
     * @param paramMap 参数Map
     * @return jdbc url
     */
    @Override
    public String generateJdbcUrl(String jdbcTemplate, String ip,
                                Integer port, String database, Map<String, String> paramMap) {
        // 生成扩展参数字符创
        String paramStr = generateParams(paramMap);
        // 替换模板中的关键字
        return jdbcTemplate.replace(JDBC_URL_TEMPLATE_IP_PLACEHOLDER, ip)
                .replace(JDBC_URL_TEMPLATE_PORT_PLACEHOLDER, String.valueOf(port))
                .replace(JDBC_URL_TEMPLATE_DATABASE_PLACEHOLDER, database)
                .replace(JDBC_URL_TEMPLATE_EXT_PARAMS_PLACEHOLDER, paramStr);
    }

    /**
     * 拼接数据库参数
     * @param url 地址
     * @param map 参数集合
     * @return 数据库链接地址
     */
    @Override
    public String generateFullJdbcUrl(String url, Map<String, String> map) {
        return url + generateParams(map);
    }


    /**
     * 根据Map生成Url的参数
     *
     * @param map
     *        参数Map
     * @return
     *        数据库参数
     */
    @Override
    public String generateParams(Map<String, String> map) {
        StringBuffer sb = new StringBuffer();
        if (Objects.nonNull(map) && !map.isEmpty()) {
            for (Map.Entry<String, String> entry : map.entrySet()){
                if (StringUtils.hasLength(sb.toString())) {
                    sb.append("&");
                } else {
                    sb.append("?");
                }
                sb.append(entry.getKey()).append("=")
                    .append(URLEncoder.encode(
                        entry.getValue(), StandardCharsets.UTF_8));
            }
        }
        return sb.toString();
    }

    /**
     * 获取数据库资源库外部节点数据
     * @param dataBaseInfo 数据库信息
     * @return 外部节点数据
     */
    @Override
    public List<ExternalDirectoryDto> getDbRepoExternalDirectory(DataBaseInfo dataBaseInfo) {

        List<ExternalDirectoryDto> directoryDtoLists = new ArrayList<>();

        // 获取数据源
        DruidDataSource ds = getDruidDataSource(dataBaseInfo);

        try {
            // 使用数据源，主流数据库能根据驱动自动识别方言
            Db db = Db.use(ds);
            // 获取外部节点数据
            directoryDtoLists.addAll(db.findAll(
                    Entity.create(DIRECTORY_TABLE_NAME), ExternalDirectoryDto.class));

        } catch (Exception e) {
            log.error("获取数据库资源库外部节点数据失败", e);
            throw new BusinessException("远程资源库连接异常，请检查资源库！");
        } finally {
            try {
                // 数据库连接关闭
                ds.close();
            } catch (Exception e) {
                log.error("动态数据库关闭异常！", e);
            }
        }

        return directoryDtoLists;
    }


    /**
     * 获取外部树目录下内容
     * @param dataBaseInfo 数据库信息
     * @param directoryId 目录id
     * @param resourceType 资源类型
     * @return 外部树目录下内容
     */
    @Override
    public ExternalFileDto getDbRepoExternalFile(DataBaseInfo dataBaseInfo,
                                                 Long directoryId, Integer resourceType) {
        List<ExternalJobDto> jobs = new ArrayList<>();
        List<ExternalTransformationDto> transformations = new ArrayList<>();
        if (Objects.isNull(resourceType)) {
            transformations = getDbRepoTransformationList(dataBaseInfo, directoryId);
            jobs = getDbRepoExternalJobList(dataBaseInfo, directoryId);
        } else {
            switch (resourceType) {
                // 作业
                case 1:
                    jobs = getDbRepoExternalJobList(dataBaseInfo, directoryId);
                    break;
                // 转换
                case 2:
                    transformations
                           = getDbRepoTransformationList(dataBaseInfo, directoryId);
                    break;
                // 默认值
                default:
                    transformations
                            = getDbRepoTransformationList(dataBaseInfo, directoryId);
                    jobs = getDbRepoExternalJobList(dataBaseInfo, directoryId);
            }
        }

        return ExternalFileDto
                      .builder().jobs(jobs).transformations(transformations).build();

    }


    /**
     * 获取数据库资源库的作业集合
     * @param dataBaseInfo 数据库信息
     * @param directoryId 目录id
     * @return 作业集合
     */
    @Override
    public List<ExternalJobDto> getDbRepoExternalJobList(DataBaseInfo dataBaseInfo, Long directoryId) {

        // 获取数据源
        DruidDataSource ds = getDruidDataSource(dataBaseInfo);

        try {
            // 使用数据源，主流数据库能根据驱动自动识别方言
            Db db = Db.use(ds);

            // 获取作业数据
            return db.findAll(Entity.create(JOB_TABLE_NAME)
                        .set("ID_DIRECTORY", directoryId), ExternalJobDto.class);
        } catch (Exception e) {
            log.error("获取数据库资源库的作业集合失败", e);
            throw new BusinessException("远程资源库连接异常，请检查资源库！");
        } finally {
            try {
                // 数据库连接关闭
                ds.close();
            } catch (Exception e) {
                log.error("动态数据库关闭异常！", e);
            }
        }
    }

    /**
     * 获取数据库资源库的转换集合
     * @param dataBaseInfo 数据库信息
     * @param directoryId 目录id
     * @return 转换集合
     */
    @Override
    public List<ExternalTransformationDto>
                             getDbRepoTransformationList(DataBaseInfo dataBaseInfo, Long directoryId) {

        // 获取数据源
        DruidDataSource ds = getDruidDataSource(dataBaseInfo);

        try {
            // 使用数据源，主流数据库能根据驱动自动识别方言
            Db db = Db.use(ds);

            // 获取transformation数据
            return db.findAll(Entity.create(TRANSFORMATION_TABLE_NAME).set("ID_DIRECTORY", directoryId),
                    ExternalTransformationDto.class);
        } catch (Exception e) {
            log.error("获取数据库资源库的作业集合失败", e);
            throw new BusinessException("远程资源库连接异常，请检查资源库！");
        } finally {
            try {
                // 数据库连接关闭
                ds.close();
            } catch (Exception e) {
                log.error("动态数据库关闭异常！", e);
            }
        }
    }

}
